﻿using Anderson.NetLobby.Common;
using System.Net;
using System.Net.Sockets;
using System;
using System.Threading;
using Anderson.NetLobby.MessageContent;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Anderson.NetLobby.Client
{
    public class NetLobbyClientEx
    {
        #region Properties
        private INetLobbyLog logger;

        private string m_serverHost;
        private int m_serverPort;
        private string m_userCode;

        private Socket clientSocket;
        private NetLobbySession session;
        private CancellationTokenSource cancelToket = new CancellationTokenSource();
        #endregion

        #region Status
        private bool m_connecting;
        private bool m_connected;
        private bool m_disconnecting;

        private bool m_confirming;
        private bool m_confirmd;

        private bool m_roomEntering;
        private bool m_roomEntered;

        private bool m_roomCreating;
        private bool m_roomCreated;
        private bool m_isRoomOwner;

        private long m_roomId;

        private long m_playerId;

        public long PlayerId
        {
            get
            {
                return m_playerId;
            }

            set
            {
                m_playerId = value;
            }
        }

        public long RoomId
        {
            get
            {
                return m_roomId;
            }

            set
            {
                m_roomId = value;
            }
        }
        #endregion

        #region Event
        ConcurrentQueue<HeartCallback> queueHeart;
        HeartCallback heartCallback;

        public Action<long> RoomStarted;
        public Action<long> RoomStoped;
        public Action<byte[], long, uint, uint, uint> BusinessMessageReceive;
        #endregion

        #region Constructor
        public NetLobbyClientEx(string host, int port, string userCode, INetLobbyLog logger,
            HeartCallback heartCallback, ConcurrentQueue<HeartCallback> queueHeart)
        {
            this.logger = logger;
            this.heartCallback = heartCallback;
            this.queueHeart = queueHeart;

            m_serverPort = port;
            m_serverHost = host;
            m_userCode = userCode;

            ResetAllPropertiesToDefault();
        }

        private void ResetAllPropertiesToDefault()
        {
            clientSocket = null;
            session = null;

            //status initing
            m_connecting = false;
            m_connected = false;
            m_disconnecting = false;

            m_confirming = false;
            m_confirmd = false;

            m_playerId = 0L;

            ResteRoomPropertiesToDefault();
        }

        private void ResteRoomPropertiesToDefault()
        {
            if (null != session)
            {
                session.roomId = 0;
                session.playerInRoom = false;
            }

            m_roomId = 0;
            m_roomEntered = false;            

            m_roomCreated = false;
            m_isRoomOwner = false;
            m_roomEntering = false;
            m_roomCreating = false;
        }

        #endregion

        #region Func
        public void Connect()
        {
            IPAddress ipAddress = IPAddress.Parse(m_serverHost);
            IPEndPoint serverEndPoint = new IPEndPoint(ipAddress, m_serverPort);

            m_connecting = true;
            clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientSocket.BeginConnect(serverEndPoint, OnConnectCallback, clientSocket);
        }

        private void OnConnectCallback(IAsyncResult ar)
        {
            m_connecting = false;

            try
            {
                Socket handler = (Socket)ar.AsyncState;
                handler.EndConnect(ar);
                logger.LogFormat("Connected to {0}", handler.RemoteEndPoint.ToString());

                m_connected = true;

                session = new NetLobbySession(handler, "", 0, DateTime.Now, logger);
                handler.BeginReceive(session.buffer, 0, NetLobbySession.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), session);
            }
            catch (Exception e)
            {
                m_connected = false;

                logger.LogError(e.Message);
            }
        }

        //int readCallback = 0;
        private void ReadCallback(IAsyncResult ar)
        {
            //DEBUG; Comment it.
            //Interlocked.Increment(ref readCallback);
            //logger.LogFormat("Client - ReadCallback = {0}", readCallback);

            NetLobbySession session = (NetLobbySession)ar.AsyncState;
            Socket handler = session.playerSocket;

            bool socketDisconnectOrError = false;
            try
            {
                int bytesRead = handler.EndReceive(ar);

                if (bytesRead > 0)
                {
                    byte[] readBytes = new byte[bytesRead];
                    Buffer.BlockCopy(session.buffer, 0, readBytes, 0, bytesRead);
                    Buffer.SetByte(session.buffer, 0, 0);

                    byte[] fullMessageBytes = session.ReceiveMessage(readBytes);
                    if (null != fullMessageBytes)
                    {
                        AnalyzeNetMessage(fullMessageBytes);
                    }
                }
                else
                {
                    socketDisconnectOrError = true;
                }
            }
            catch (SocketException e)
            {
                logger.LogError(e.Message);

                if (e.SocketErrorCode == SocketError.HostDown)
                {
                    socketDisconnectOrError = true;
                }
            }

            if (socketDisconnectOrError)
            {
                logger.Log("Server Disconnect.");

                Disconnect();
            }
            else
            {
                handler.BeginReceive(session.buffer, 0, NetLobbySession.BufferSize, SocketFlags.None, new AsyncCallback(ReadCallback), session);
            }
        }

        #region 收到的消息分析
        private void AnalyzeNetMessage(byte[] fullMessageBytes)
        {
            //DEBUG; Comment it;
            //StringBuilder s = new StringBuilder();
            //for (int i = 0; i < fullMessageBytes.Length; i++)
            //{
            //    s.Append(fullMessageBytes[i]);
            //}
            //logger.Log(s.ToString());

            bool error = false;
            //反序列化
            try
            {
                int sizeOfUInt32 = sizeof(uint);
                int sizeOfLong = sizeof(long);
                int size = sizeOfUInt32 * 6 + sizeOfLong;
                int count = fullMessageBytes.Length - size;

                ByteStream byteStream = new ByteStream(fullMessageBytes);

                uint length = byteStream.ReadUInt32();
                uint bof = byteStream.ReadUInt32();
                uint l1 = byteStream.ReadUInt32();
                uint l2 = byteStream.ReadUInt32();
                uint l3 = byteStream.ReadUInt32();
                long roomId = byteStream.ReadInt64();
                byte[] content = byteStream.ReadBytes(count);
                uint eof = byteStream.ReadUInt32();

                if (NetLobbyDefination.NET_MESSAGE_BOF != bof)
                {
                    logger.LogError("AnalyzeNetMessage BOF Error.");
                    error = true;
                }
                else if (NetLobbyDefination.NET_MESSAGE_EOF != eof)
                {
                    logger.LogError("AnalyzeNetMessage EOF Error.");
                    error = true;
                }
                else if (null == content || content.Length == 0)
                {
                    logger.LogError("AnalyzeNetMessage Content Length Error.");
                    error = true;
                }

                if (!error)
                {
                    ReceiveNetMessage(fullMessageBytes, roomId, l1, l2, l3);
                }
            }
            catch (Exception e)
            {
                logger.LogError(e.Message);
            }
        }

        private void ReceiveNetMessage(byte[] fullMessageBytes, long roomId, uint l1, uint l2, uint l3)
        {
            uint v = l1 & MessageLevelDefinition.MASK_CLIENT_L1_BIZ;
            if (v == MessageLevelDefinition.CLIENT_L1_BIZ)
            {
                ReceiveNetMessage_Business(fullMessageBytes, roomId, l1, l2, l3);

                return;
            }

            switch (l1)
            {
                case MessageLevelDefinition.SERVER_L1_SYSTEM:
                    ReceiveNetMessage_System(fullMessageBytes, roomId, l1, l2, l3);
                    break;
                case MessageLevelDefinition.CLIENT_L1_ROOM:
                    ReceiveNetMessage_Room(fullMessageBytes, roomId, l1, l2, l3);
                    break;
                default:
                    logger.LogError("ReceiveNetMessage : Unkonwn L1 Type");
                    break;
            }
        }

        private void ReceiveNetMessage_Business(byte[] fullMessageBytes, long roomId, uint l1, uint l2, uint l3)
        {
            logger.Log("Client.ReceiveNetMessage_Business");
            BusinessMessageReceive?.Invoke(fullMessageBytes, roomId, l1, l2, l3);
        }

        private void ReceiveNetMessage_System(byte[] fullMessageBytes, long roomId, uint l1, uint l2, uint l3)
        {
            if (l2 == MessageLevelDefinition.SERVER_L2_SYSTEM_PLAYERID)
            {
                if (l3 == MessageLevelDefinition.SERVER_L3_SYSTEM_PLAYERID_CMD)
                {
                    PlayerIdClientConfirm(fullMessageBytes);
                    return;
                }
            }

            if (l2 == MessageLevelDefinition.SERVER_L2_SYSTEM_HEART)
            {
                if (l3 == MessageLevelDefinition.SERVER_L3_SYSTEM_HEART)
                {
                    NetMessage<MC_Heart> netMessage = MessageSerialize.Deserialize<MC_Heart>(fullMessageBytes);
                    MC_Heart content = netMessage.MessageContent;

                    //logger.LogFormat("FrameCount = {0}", content.frameCount);
                    queueHeart.Enqueue(new HeartCallback(heartCallback.action, content.frameCount));

                    return;
                }
            }
        }

        private void PlayerIdClientConfirm(byte[] fullMessageBytes)
        {
            logger.Log("Server.PlayerIdClientConfirm");

            //DONE; Send ConfirmId
            NetMessage<MC_PlayerId> netMessage = MessageSerialize.Deserialize<MC_PlayerId>(fullMessageBytes);
            MC_PlayerId setPlayerId = netMessage.MessageContent;
            setPlayerId.idFromClient = setPlayerId.idFromServer;
            setPlayerId.codeFromClient = m_userCode;

            m_playerId = setPlayerId.idFromServer;

            NetMessage<MC_PlayerId> netMessageSend = new NetMessage<MC_PlayerId>(
                0,
                MessageLevelDefinition.SERVER_L1_SYSTEM,
                MessageLevelDefinition.SERVER_L2_SYSTEM_PLAYERID,
                MessageLevelDefinition.CLIENT_L3_SYSTEM_PLAYERID_CFM,
                setPlayerId);

            m_confirming = true;
            logger.LogFormat("Server.PlayerIdClientConfirm, id = {0}", setPlayerId.idFromServer);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendPlayerIdConfirmCallBack), session);
        }

        private void ReceiveNetMessage_Room(byte[] fullMessageBytes, long roomId, uint l1, uint l2, uint l3)
        {
            if (l2 == MessageLevelDefinition.CLIENT_L2_ROOM_CREATE)
            {
                if (l3 == MessageLevelDefinition.SERVER_L3_ROOM_CREATE_RES)
                {
                    CreateRoomResponse(fullMessageBytes, roomId);
                    return;
                }
            }

            if (l2 == MessageLevelDefinition.CLIENT_L2_ROOM_LIST)
            {
                if (l3 == MessageLevelDefinition.SERVER_L3_ROOM_LIST_GET_RES || l3 == MessageLevelDefinition.SERVER_L3_ROOM_LIST_CHANGED)
                {
                    GetRoomListResponse(fullMessageBytes);
                    return;
                }
            }

            if (l2 == MessageLevelDefinition.CLIENT_L2_ROOM_ACTION)
            {
                if (l3 == MessageLevelDefinition.SERVER_L3_ROOM_ENTER_RES)
                {
                    EnterRoomResponse(fullMessageBytes);
                    return;
                }

                if (l3 == MessageLevelDefinition.SERVER_L3_ROOM_START_RES)
                {
                    StartRoomResponse(fullMessageBytes, roomId);
                    return;
                }

                if (l3 == MessageLevelDefinition.SERVER_L3_ROOM_CLOSE_RES)
                {
                    CloseRoomResponse(fullMessageBytes, roomId);
                    return;
                }
            }
        }

        private void EnterRoomResponse(byte[] fullMessageBytes)
        {
            NetMessage<MC_RoomAction> netMessage = MessageSerialize.Deserialize<MC_RoomAction>(fullMessageBytes);
            MC_RoomAction roomAction = netMessage.MessageContent;

            logger.LogFormat("Server.EnterRoomResponse, actionResult = {0}", roomAction.actionResult);
            if (roomAction.actionResult == 1)
            {
                long roomId = roomAction.roomId;
                m_roomId = roomId;

                m_roomEntered = true;
                session.roomId = roomId;
                session.playerInRoom = true;

                logger.LogFormat("Enter room {0} success.", roomId);
            }
            else
            {
                logger.LogFormat("Enter room falied, error = {0}", roomAction.actionResult);
            }
        }

        private void StartRoomResponse(byte[] fullMessageBytes, long roomId)
        {
            logger.LogFormat("*** room {0} all player started, do something...", roomId);

            RoomStarted?.Invoke(roomId);
        }

        private void CloseRoomResponse(byte[] fullMessageBytes, long roomId)
        {
            logger.LogFormat("*** room {0} closed, do something...", roomId);

            ResteRoomPropertiesToDefault();

            RoomStoped?.Invoke(roomId);
        }

        private void GetRoomListResponse(byte[] fullMessageBytes)
        {
            NetMessage<MC_RoomList> netMessage = MessageSerialize.Deserialize<MC_RoomList>(fullMessageBytes);
            MC_RoomList roomList = netMessage.MessageContent;

            string msg = "";
            logger.LogFormat("Server.GetRoomListResponse, Count = {0}", roomList.roomInfos.list.Count);

            List<long> roomPlayerStartList = null;
            bool playerStated = false;
            foreach (RoomInfo item in roomList.roomInfos.list)
            {
                roomPlayerStartList = item.playerStartIds.list;

                msg += string.Format("Room[{0}] = owner={1}, stated={2}, count={3}\r\n", item.roomId, item.roomOwner, item.roomStarted, item.playerCount);
                foreach (long id in item.playerIds.list)
                {
                    playerStated = roomPlayerStartList.Contains(id);
                    msg += string.Format("\t playerId = {0}, start={1};\r\n", id, playerStated);
                }
            }

            if (!"".Equals(msg))
            {
                logger.Log(msg);
            }
        }

        private void CreateRoomResponse(byte[] fullMessageBytes, long roomId)
        {
            NetMessage<MC_RoomCreate> netMessage = MessageSerialize.Deserialize<MC_RoomCreate>(fullMessageBytes);
            MC_RoomCreate roomCreateRes = netMessage.MessageContent;

            if (roomCreateRes.actionResult == 1)
            {
                m_roomCreated = true;
                m_isRoomOwner = true;
                m_roomEntered = true;
                m_roomId = roomId;

                session.playerInRoom = true;
                session.roomId = roomId;
            }
            logger.LogFormat("Server.CreateRoomResponse, ActionResult = {0}, RoomId = {1}", roomCreateRes.actionResult, roomCreateRes.roomId);

        }
        #endregion

        private void SendCallBack(IAsyncResult ar)
        {
            try
            {
                NetLobbySession session = (NetLobbySession)ar.AsyncState;
                Socket handler = session.playerSocket;

                handler.EndSend(ar);
            }
            catch (Exception e)
            {
                logger.LogError(e.Message);
            }
        }

        private void SendPlayerIdConfirmCallBack(IAsyncResult ar)
        {
            m_confirming = false;
            m_confirmd = true;

            try
            {
                NetLobbySession session = (NetLobbySession)ar.AsyncState;
                Socket handler = session.playerSocket;

                handler.EndSend(ar);
            }
            catch (Exception e)
            {
                logger.LogError(e.Message);
            }
        }

        public void Disconnect()
        {
            if (m_connecting || !m_connected)
            {
                return;
            }

            try
            {
                session.ClientSideClose();

                ResetAllPropertiesToDefault();
            }
            catch (Exception e)
            {
                logger.LogError(e.Message);
            }
        }

        public void CreateRoom()
        {
            if (m_connected && m_confirmd)
            {
                DoCreateRoom();
            }
        }

        public void ListRoom()
        {
            if (m_connected && m_confirmd)
            {
                DoListRoom();
            }
        }

        public void EnterRoom(long roomId)
        {
            if (m_connected && m_confirmd)
            {
                DoEnterRoom(roomId);
            }
        }

        public void ExitRoom()
        {
            if (m_connected && m_confirmd && m_roomEntered)
            {
                DoExitRoom();
            }
        }

        public void StartRoom()
        {
            if (m_connected && m_confirmd && m_roomEntered)
            {
                DoStartRoom();
            }
        }

        public void CloseRoom()
        {
            if (m_connected && m_confirmd && m_roomEntered && m_isRoomOwner)
            {
                DoCloseRoom();
            }
        }
        #endregion

        #region SendMessage
        internal void SendBusinessNetMessage(byte[] bytes)
        {
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);
        }

        private void DoCreateRoom()
        {
            if (m_roomCreating || m_roomCreated)
            {
                return;
            }
            m_roomCreating = true;

            logger.Log("Client.DoCreateRoom");

            MC_RoomCreate roomCreate = new MC_RoomCreate();
            roomCreate.playerId = m_playerId;

            NetMessage<MC_RoomCreate> netMessageSend = new NetMessage<MC_RoomCreate>(
                0,
                MessageLevelDefinition.CLIENT_L1_ROOM,
                MessageLevelDefinition.CLIENT_L2_ROOM_CREATE,
                MessageLevelDefinition.CLIENT_L3_ROOM_CREATE_REQ,
                roomCreate);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);

            m_roomCreating = false;
        }

        private void DoListRoom()
        {
            logger.Log("Client.DoListRoom");

            MC_RoomAction roomAction = new MC_RoomAction();
            roomAction.playerId = m_playerId;

            NetMessage<MC_RoomAction> netMessageSend = new NetMessage<MC_RoomAction>(
                0,
                MessageLevelDefinition.CLIENT_L1_ROOM,
                MessageLevelDefinition.CLIENT_L2_ROOM_LIST,
                MessageLevelDefinition.CLIENT_L3_ROOM_LIST_GET_REQ,
                roomAction);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);
        }

        private void DoEnterRoom(long roomId)
        {
            if (m_roomEntering || m_roomEntered)
            {
                return;
            }

            m_roomEntering = true;

            logger.LogFormat("Client.DoEnterRoom : {0}", roomId);

            MC_RoomAction roomAction = new MC_RoomAction();
            roomAction.playerId = m_playerId;

            NetMessage<MC_RoomAction> netMessageSend = new NetMessage<MC_RoomAction>(
                roomId,
                MessageLevelDefinition.CLIENT_L1_ROOM,
                MessageLevelDefinition.CLIENT_L2_ROOM_ACTION,
                MessageLevelDefinition.CLIENT_L3_ROOM_ENTER_REQ,
                roomAction);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);

            m_roomEntering = false;
        }

        private void DoExitRoom()
        {
            logger.Log("Client.DoExitRoom");

            long tempRoomId = m_roomId;
            ResteRoomPropertiesToDefault();

            MC_RoomAction roomAction = new MC_RoomAction();
            roomAction.playerId = m_playerId;

            NetMessage<MC_RoomAction> netMessageSend = new NetMessage<MC_RoomAction>(
                tempRoomId,
                MessageLevelDefinition.CLIENT_L1_ROOM,
                MessageLevelDefinition.CLIENT_L2_ROOM_ACTION,
                MessageLevelDefinition.CLIENT_L3_ROOM_EXIT_REQ,
                roomAction);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);            
        }

        private void DoStartRoom()
        {
            logger.Log("Client.DoStartRoom");

            MC_RoomAction roomAction = new MC_RoomAction();
            roomAction.playerId = m_playerId;

            NetMessage<MC_RoomAction> netMessageSend = new NetMessage<MC_RoomAction>(
                m_roomId,
                MessageLevelDefinition.CLIENT_L1_ROOM,
                MessageLevelDefinition.CLIENT_L2_ROOM_ACTION,
                MessageLevelDefinition.CLIENT_L3_ROOM_START_REQ,
                roomAction);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);
        }

        private void DoCloseRoom()
        {
            logger.Log("Client.DoCloseRoom");

            long tempRoomId = m_roomId;
            long tempPlayerId = m_playerId;

            ResteRoomPropertiesToDefault();

            MC_RoomAction roomAction = new MC_RoomAction();
            roomAction.playerId = tempPlayerId;

            NetMessage<MC_RoomAction> netMessageSend = new NetMessage<MC_RoomAction>(
                tempRoomId,
                MessageLevelDefinition.CLIENT_L1_ROOM,
                MessageLevelDefinition.CLIENT_L2_ROOM_ACTION,
                MessageLevelDefinition.CLIENT_L3_ROOM_CLOSE_REQ,
                roomAction);

            byte[] bytes = MessageSerialize.Serialize(netMessageSend);
            session.playerSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, new AsyncCallback(SendCallBack), session);
        }
        #endregion
    }
}
